{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Error Analysis\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "\n",
    "This tutorial will use the example of a Cross Resonance (CR) gate to demonstrate how to analyze errors that arise during a quantum operation, using tools including dynamical analysis to understand the state evolution, and the truth table to visualize the leakage errors outside of the computational space. The outline of this tutorial is as follows:\n",
    "- Introduction\n",
    "- Preparation\n",
    "- Construct Hamiltonian and pulse optimization\n",
    "- Dynamical analysis\n",
    "- Truth table\n",
    "- Summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "When a target quantum gate is implemented using control pulses, the actual quantum operation might differ from the target operation. This difference is normally quantified as infidelity. Understanding the sources of errors that lead to this infidelity is important for us to design pulse sequences that can mitigate these errors and improve the gate fidelity. \n",
    "\n",
    "Quanlse provides the functionality to analyze the population evolution of qubit states through dynamical analysis, and visualize the leakage out of the computational space through a truth table. In this tutorial, we will demonstrate these two functions using the implementation of Cross Resonance (CR) gate as an example. More details on CR gate can be found in [Cross Resonance gate](https://quanlse.baidu.com/#/doc/tutorial-cr)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "\n",
    "After you have successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This module is imported for creating Hamiltonian dictionary\n",
    "from Quanlse.Utils import Hamiltonian as qham\n",
    "\n",
    "# These functions are imported to define useful operators matrices to free us from defining them manually\n",
    "from Quanlse.Utils.Operator import number, duff, dagger, driveX, driveY, basis, projector\n",
    "\n",
    "# These functions are imported to helps us perform matrix calculation\n",
    "from Quanlse.Utils.Tools import tensor, project\n",
    "\n",
    "# This function is imported to perform CR gate optimization using Quanlse Cloud Service\n",
    "from Quanlse.remoteOptimizer import remoteOptimizeCr\n",
    "\n",
    "# This module is imported to define frequently-used matrix form for quantum gates\n",
    "from Quanlse.QOperation import FixedGate\n",
    "\n",
    "# This module is imported to perform figure plotting\n",
    "from Quanlse.Utils import Plot\n",
    "\n",
    "import numpy\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use `remoteOptimizeCr()` function, we need a token to get access to Quanlse Cloud Service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import Define class and set the token\n",
    "# Please visit http://quantum-hub.baidu.com\n",
    "from Quanlse import Define\n",
    "Define.hubToken = \"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construct Hamiltonian and pulse optimization\n",
    "\n",
    "Using the following lines of code, we first construct our Hamiltonian and optimize the control parameters to implement a high-fidelity CR gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define parameters to initialize the Hamiltonian\n",
    "dt = 2.0  # Sampling period\n",
    "qubits = 2  # Number of qubits\n",
    "level = 3  # Energy level\n",
    "\n",
    "# Define qubit parameters\n",
    "g = 0.0038 * (2 * numpy.pi)  # Coupling strength, GHz\n",
    "wq0  = 4.914 * (2 * numpy.pi)  # Transition frequency for qubit 0, GHz\n",
    "wq1 = 4.714 * (2 * numpy.pi)  # Transition frequency for qubit 1, GHz\n",
    "wd1 = wq1  # Drive frequency is the frequency for qubit 1\n",
    "anharm0 = - 0.33 * (2 * numpy.pi)  # Anharmonicity of qubit 0, GHz\n",
    "anharm1 = - 0.33 * (2 * numpy.pi)  # Anharmonicity of qubit 1, GHz\n",
    "\n",
    "# Initialize the Hamiltonian\n",
    "ham = qham.createHam(title=\"cr-gate\", dt=dt, qubitNum=qubits, sysLevel=level)\n",
    "\n",
    "# Add the detuning terms\n",
    "term_detuning0 = (wq0 - wd1) * number(level)\n",
    "term_detuning1 = (wq1 - wd1) * number(level)\n",
    "\n",
    "qham.addDrift(ham,name='detuning0', onQubits=0, matrices=term_detuning0)\n",
    "qham.addDrift(ham,name='detuning1', onQubits=1, matrices=term_detuning1)\n",
    "\n",
    "# Add the anharmonicity terms\n",
    "qham.addDrift(ham, name='anharm0', onQubits=0, matrices=duff(level), amp=anharm0 / 2)\n",
    "qham.addDrift(ham, name='anharm1', onQubits=1, matrices=duff(level), amp=anharm1 / 2)\n",
    "\n",
    "# Add the coupling term\n",
    "qham.addCoupling(ham, \"coupling\", [0, 1], g=g / 2)\n",
    "\n",
    "# Add the control terms\n",
    "qham.addControl(ham, name=\"q0-ctrlx\", onQubits=0, matrices=driveX(level))\n",
    "qham.addControl(ham, name=\"q0-ctrly\", onQubits=0, matrices=driveY(level))\n",
    "\n",
    "# Set amplitude bound\n",
    "aBound = (1.0, 3.0)\n",
    "\n",
    "# Run the optimization on Quanlse Cloud Service\n",
    "ham, infidelity = remoteOptimizeCr(ham, aBound=aBound, tg=200, maxIter=3, targetInfidelity=0.01)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dynamical analysis\n",
    "\n",
    "Studying how the initial qubit state evolves during a quantum operation helps us to understand the effect of control pulses on the qubit and the sources of errors. The dynamical analysis functionality in Quanlse simulates the population evolution of different qubit states for a given initial state. In the following example, we demonstrate how to simulate the population evolution of basis states in the computational space for a two-qubit system ($|00\\rangle$, $|01\\rangle$, $|10\\rangle$ and $|11\\rangle$) when a CR gate is implemented. We will look at the population evolution of basis states when the initial qubit state is $|01\\rangle$ as an example."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We first define our complete set of computational basis states for a two-qubit system by firstly generating a state vector for each qubit using `basis` function, which takes the number of energy levels (3 for a three-level system) as the first input, and the state (0 or 1 in this case) as the second input. The state vectors representing these two qubits are then constructed from each state vector using `tensor` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define basis states: 00, 01, 10 and 11\n",
    "state00 = tensor([basis(3, 0), basis(3, 0)])\n",
    "state01 = tensor([basis(3, 0), basis(3, 1)])\n",
    "state10 = tensor([basis(3, 1), basis(3, 0)])\n",
    "state11 = tensor([basis(3, 1), basis(3, 1)])\n",
    "stateList = [state00, state01, state10, state11]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We then construct the projection matrices to calculate the population for each basis state from the expectation value of each projection matrix. We use `projector` function to create the projection matrix. `projector(a, b)` takes two previously defined state vetors (a and b) and generates the projection matrix $|a\\rangle\\langle b|$. If only one state vector is taken as the input, for example, $|a\\rangle$, this function will return $|a\\rangle\\langle a|$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct projection matrices from basis states\n",
    "matrix00 = projector(state00)\n",
    "matrix01 = projector(state01)\n",
    "matrix10 = projector(state10)\n",
    "matrix11 = projector(state11)\n",
    "matrixList = [matrix00, matrix01, matrix10, matrix11]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are now ready to evaluate the evolution of the expectation value for each projection matrix during the CR gate operation. We use the function `Benchmark.evolution` to do so. This function takes the following inputs: the constructed Hamiltonian, the list of initial state vectors and the list of projection matrices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the simulation to evaluate evolution of the expectation values\n",
    "from Quanlse.Utils import Benchmark\n",
    "evolutionResult = Benchmark.evolution(ham, stateInitial=stateList, matrix=matrixList)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The returned **result** is a dictionary containing various terms. The keys *'0'*, *'1'*, *'2'* … refer to the indices of the initial states. Corresponding to each key for state index, there is a sub-dictionary that contains the following terms:\n",
    "\n",
    "* 'state_form': the initial state vector\n",
    "* 'state_evolution_history': the evolution of the initial state vector\n",
    "* 'result': the evolution of the expectation values for the projection matrices corresponding to different states. Within this sub-dictionary, it contains the expectation value for each projection matrix."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can plot the population evolution of different basis states by taking the absolute value of each expectation value. In the following lines, we plot the population evolution as a function of time with an initial state defined to be $|01\\rangle$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define x values to be the time for evolution\n",
    "x = numpy.linspace(0, ham['circuit']['max_time_ns'], ham['circuit']['max_time_dt'])\n",
    "\n",
    "# Define y values to be the expectation value for each projection matrix when the initial state is in 01, which corresponds to index 1\n",
    "y1 = numpy.array(evolutionResult['1']['result']['matrix-0-value'])\n",
    "y2 = numpy.array(evolutionResult['1']['result']['matrix-1-value'])\n",
    "y3 = numpy.array(evolutionResult['1']['result']['matrix-2-value'])\n",
    "y4 = numpy.array(evolutionResult['1']['result']['matrix-3-value'])\n",
    "\n",
    "# Plot the population as absolute value of the expectation values\n",
    "plt.plot(x, abs(y1), linewidth=3, label='00')\n",
    "plt.plot(x, abs(y2), linewidth=3, label='01')\n",
    "plt.plot(x, abs(y3), linewidth=3, label='10')\n",
    "plt.plot(x, abs(y4), linewidth=3, label='11')\n",
    "plt.title(r'Population Evolution for $|01\\rangle$ Initial State ')\n",
    "plt.xlabel('Time (ns)')\n",
    "plt.ylabel('Population')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Truth table\n",
    "\n",
    "A truth table of a quantum gate contains the probability of the system being in each possible basis states at the end of an operation for each possible initial state. Initial states are always selected to be in the computational space, while the final states can be outside of the computational space, which correponds to a leakage event. Quanlse provides the functionality to calculate the elements of the truth table for a quantum operation and visualize the results using a 2D plot. This is a convenient tool for us to analyze the errors corresponding to the leakage for all initial basis states.\n",
    "\n",
    "Here, we will introduce this tool by analyzing the unitary operator of the CR gate, which is generated in the *Construct Hamiltonian and pulse optimization* section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import the function for generating the truth table\n",
    "from Quanlse.Utils.Benchmark import stateTruthTable\n",
    "\n",
    "# Import the function for plotting the heat map\n",
    "from Quanlse.Utils.Plot import plotHeatMap\n",
    "\n",
    "# Import the functions for basis operations\n",
    "from Quanlse.Utils.Tools import generateBasisIndexList, computationalBasisList\n",
    "\n",
    "# Indicate the input state list, and generate the list of the state indices\n",
    "inputStateStr = ['00', '01', '10', '11']\n",
    "initStateList = generateBasisIndexList(inputStateStr, level)\n",
    "\n",
    "# Generate the matrix of the truth table\n",
    "result = qham.simulate(ham)\n",
    "matrix = stateTruthTable(result[\"unitary\"], qubits, level, initStateList)\n",
    "\n",
    "# Generate the list of the output state strings and plot the heat map\n",
    "outputStateStr = computationalBasisList(qubits, level)\n",
    "plotHeatMap(matrix, xTicks=outputStateStr, yTicks=inputStateStr, xLabel=\"Output State\", yLabel=\"Input State\", useLog=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From the above heat map, we can easily obtain the information about dynamical evolution and the leakage error. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In summary, this tutorial illustrated the Quanlse functionalities for analyzing errors that might arise when a quantum gate is implemented through control pulses. Dynamical analysis gives us a complete picture of the state evolution, and truth table informs us the distribution of errors on different states.\n",
    "\n",
    "After reading this tutorial on error analysis, users are encouraged to explore other advanced research techniques that are not shown in this tutorial."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## References\n",
    "\\[1\\] Nielsen, Michael A., and Isaac L. Chuang. Quantum Computation and Quantum Information: 10th Anniversary Edition. Cambridge University Press, 2010."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
